﻿#pragma once
#include "lix/lix.h"


namespace lix{


	//! 固定長メモリチャンク
	//! 
	//! 固定長のメモリを予め確保しておき、使いまわすためのメモリチャンク。
	//! このオブジェクト内に ItemSize バイトのオブジェクトが最大 255 個格納される。
	//! 
	//! @tparam ItemSize 各ブロックのサイズ。
	//!		FixedMemoryChunk 内ではブロックは byte 配列として連続確保し切り出してくため
	//!		使用する側がアライメントなどを考慮して適切な大きさを指定すること。
	//!	@tparam ItemCount このチャンクで確保するブロックの数
	template< 
		unsigned char ItemSize,
		unsigned char ItemCount 
	> 
	class FixedMemoryChunk {
	public:
		// LIX_STATIC_ASSERT( ItemCount <= 255 && ItemCount != 0 );

		enum { kItemCount = ItemCount };
		enum { kItemSize  = ItemSize  };


		//! コンストラクタ。
		//! 
		//! 二段生成とする。使用前に create を呼ぶこと。
		FixedMemoryChunk()
			: m_pMemory(NULL)
		{}

		//! デストラクタ
		~FixedMemoryChunk()
		{
			destroy();
		}

		//! メモリ生成
		//! 
		//! メモリを確保し、チャンクとして使用するための仕込みを行う。
		//! @return メモリ確保失敗時 false
		bool create()
		{
			LIX_ASSERT( m_pMemory == NULL );

			// 使用メモリ確保
			m_pMemory = (unsigned char*) malloc( ItemSize * ItemCount );
			if(! m_pMemory ){
				return false;
			}

			// 全て未使用
			m_AvailableItemCount = ItemCount;
			m_FirstAvailableItem = 0;

			// 各未使用ブロックの先頭に『次の未使用ブロックのインデックス』を入れておき、
			// リンクリストのように使用する、そのための仕込み
			for( int i=0; i<ItemCount; i++){
				unsigned char * p = m_pMemory + ( i*ItemSize );
				*p = (i+1);
			}

			return true;
		}

		//! 破棄
		void destroy(){
			if( ! m_pMemory ){
				return;
			}
			free( m_pMemory );
			m_pMemory = NULL;
		}

		//! メモリの確保
		//!
		//! チャンクから未使用領域を返す。全て使用されている場合は NULL を返す。
		//! ここで返却されたメモリは不要になったら deallocate で解放する。
		void* allocate()
		{
			// いっぱいいっぱい。
			if(m_AvailableItemCount ==0 ){
				return NULL;
			}

			m_AvailableItemCount -= 1;
			unsigned char* item = getItem( m_FirstAvailableItem );

			// 未使用ブロックの頭には『次の未使用ブロックのインデックス』がはいっているので、
			// それを次に使うように覚える。
			m_FirstAvailableItem = *item;

			return item;
		}


		//! メモリ解放
		void deallocate( void* p)
		{
			LIX_ASSERT( m_pMemory );

			if( p == NULL ){
				return;
			}

			// 解放しようとしているブロック、
			// m_pMemory の範囲内で ItemSize で割り切れる位置であるはず
			unsigned char* item = static_cast< unsigned char*>(p);
			LIX_ASSERT( m_pMemory <= item );
			LIX_ASSERT( ((item-m_pMemory) % ItemSize ) == 0 );

			// 解放しようとしているブロックのインデックス
			unsigned char index = static_cast<unsigned char>((item-m_pMemory) / ItemSize);
			LIX_ASSERT( index < ItemCount );

			// 未使用ブロックの情報を未使用ブロック自体に書きこむという構造上、二重 deallocate は検出が難しい。
			// （未使用ブロック全部たどって無いことを確認すればいいできるけど、重いし）
			// m_FirstAvailableItem には最後に deallocate されたインデックスが入っているため、
			// それを deallocate しようとした場合は明らかにおかしいので、それだけ検出する。
			LIX_ASSERT( m_FirstAvailableItem != index );
			LIX_ASSERT( m_AvailableItemCount != ItemCount );

			// 一つ増やす
			m_AvailableItemCount += 1;

			// このブロックを未使用ブロックとして『次の未使用ブロックのインデックス』を入れ、
			// 次はこのブロックを使うよう覚える。
			*item = m_FirstAvailableItem;
			m_FirstAvailableItem = index;
		}

		//! 未使用ブロックの数を返す。
		size_t getAvailableCount() const 
		{
			LIX_ASSERT( m_pMemory );
			return m_AvailableItemCount;
		}

		//! 全部使用されているとき true 
		bool isFull() const
		{
			LIX_ASSERT( m_pMemory );
			return m_AvailableItemCount == 0;
		}

		//! 一つもメモリを使っていない（全部フリーの）とき true
		bool isUnused() const
		{
			if( m_pMemory == NULL ){
				return true;
			}
			return (m_AvailableItemCount == ItemCount);
		}

		//! 与えられたポインタが、このチャンク内のブロックであるとき true
		bool isTargetBlockMemory( void* p )
		{
			if( m_pMemory == NULL ){
				return false;
			}
			unsigned char* item = static_cast< unsigned char*>(p);
			if( m_pMemory > item ){
				return false;
			}
			if( ((item-m_pMemory) % ItemSize ) != 0 ){
				return false;
			}
			uint32_t index = uint32_t(((item-m_pMemory) / (uint32_t)ItemSize));
			if( index >= ItemCount ){
				return false;
			}
			return true;
		}

	private:
		LIX_DEF_COPY_AND_ASSIGN( FixedMemoryChunk );

		unsigned char* getItem( unsigned char index ){
			return m_pMemory + (index*ItemSize );
		}

		unsigned char * m_pMemory;			//!< メモリブロック 
		unsigned char m_FirstAvailableItem;	//!< 未使用ブロックインデックス
		unsigned char m_AvailableItemCount;	//!< 現在開いているブロックの数
	};



}